package com.sinosig.slbi.gm;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.util.Arrays;
import java.util.Base64;

/**
 * sm2加签验签 需要带userid
 * @author wangjianliang  2022/3/24 13:39
 * @ClassName: SM2Util
 */
public class SM2Util {

    static BouncyCastleProvider configProvider;
    static SM2 sm2 = SM2.Instance();
    static X9ECParameters parameters = GMNamedCurves.getByName("sm2p256v1");

    private final static int RS_LEN = 32;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 公私钥生成方法
     *
     * @return
     */
    public static SM2 generateKeyPair() {// 获取SM2椭圆曲线的参数
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");

        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg;
        try {
            configProvider = new BouncyCastleProvider();
            kpg = KeyPairGenerator.getInstance("EC", configProvider);
            // 使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            // 获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            BCECPublicKey aPublic = (BCECPublicKey) keyPair.getPublic();
            BCECPrivateKey aPrivate = (BCECPrivateKey) keyPair.getPrivate();
            sm2.setPubKey(Hex.toHexString(aPublic.getQ().getEncoded(false)));
            sm2.setPriKey(aPrivate.getD().toString(16));
            sm2.setPublicKey(keyPair.getPublic());
            sm2.setPrivateKey(keyPair.getPrivate());
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return sm2;
    }


    /**
     * 加载公钥私钥
     */
    public static SM2 loadSM2Key(String pubKey, String priKey) {
        SM2 sm2 = SM2.Instance();
        sm2.setPubKey(pubKey);
        sm2.setPriKey(priKey);
        sm2.setPublicKey(createPublicKey(pubKey));
        sm2.setPrivateKey(createPrivateKey(priKey));
        return sm2;
    }

    /**
     * 使用私钥加签
     */
    public static byte[] signSM3withSM2(PrivateKey privateKey, byte[] src) {
        Signature signer = null;
        byte[] res = null;
        try {
            signer = Signature.getInstance("SM3withSM2", "BC");
            signer.initSign(privateKey, new SecureRandom());
            signer.update(src, 0, src.length);
            res = signer.sign();
            res = rsAsn1ToPlainByteArray(res);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
        return res;
    }
    /**
     * 验签
     * @param msg
     * @param rs
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] rs, PublicKey publicKey) {
        try {
            rs = rsPlainByteArrayToAsn1(rs);
            Signature verifier = Signature.getInstance("SM3withSM2", "BC");
            verifier.initVerify(publicKey);
            verifier.update(msg, 0, msg.length);
            return verifier.verify(rs);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**


    /**
     * 加密
     * @return
     */
    public static byte[] sm2Encrypt(PublicKey publicKey,byte[] src) throws Exception {
        AsymmetricKeyParameter asymmetricKeyParameter = ECUtil.generatePublicKeyParameter(publicKey);
        SM2EngineExtend sm2EngineExtend = new SM2EngineExtend();
        ParametersWithRandom parametersWithRandom = new ParametersWithRandom(asymmetricKeyParameter, new SecureRandom());
        sm2EngineExtend.init(true,SM2EngineExtend.CIPHERMODE_NORM, parametersWithRandom);
        return sm2EngineExtend.processBlock(src,0,src.length);
    }

    /**
     * 解密
     * @return
     */
    public static byte[] sm2Decrypt(PrivateKey privateKey,byte[] src) throws InvalidKeyException, InvalidCipherTextException {
        AsymmetricKeyParameter asymmetricKeyParameter = ECUtil.generatePrivateKeyParameter(privateKey);
        SM2EngineExtend sm2EngineExtend = new SM2EngineExtend();
        sm2EngineExtend.init(false,SM2EngineExtend.CIPHERMODE_NORM, asymmetricKeyParameter);
        return sm2EngineExtend.processBlock(src,0,src.length);
    }

    /**
     * 将Base64转码的公钥串，转化为公钥对象
     *
     * @param publicKey
     * @return
     */
    public static PublicKey createPublicKey(String publicKey) {
        PublicKey publickey = null;
        try {
            if (publicKey.length() == 130 && publicKey.startsWith("04")){
                // 去掉04
                publicKey = publicKey.substring(2);
            }
            ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("sm2p256v1", new SM2P256V1Curve(), parameters.getG(), parameters.getN());
            java.security.spec.ECPoint ecPoint = new java.security.spec.ECPoint(new BigInteger(publicKey.substring(0, 64), 16), new BigInteger(publicKey.substring(64), 16));
            ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(ecPoint, ecNamedCurveSpec);
            publickey = new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return publickey;
    }

    /**
     * 将Base64转码的私钥串，转化为私钥对象
     *
     * @param privateKey
     * @return
     */
    public static PrivateKey createPrivateKey(String privateKey) {
        BCECPrivateKey privatekey = null;
        try {
            ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("sm2p256v1", parameters.getCurve(),parameters.getG(), parameters.getN());
            ECPrivateKeySpec ecPrivateKeySpec1 = new ECPrivateKeySpec(new BigInteger(privateKey, 16), ecNamedCurveSpec);
            privatekey = new BCECPrivateKey("EC", ecPrivateKeySpec1, BouncyCastleProvider.CONFIGURATION);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return privatekey;
    }
    /**
     * BC的SM3withSM2签名得到的结果的rs是asn1格式的，这个方法转化成直接拼接r||s
     * @param rsDer rs in asn1 format
     * @return sign result in plain byte array
     */
    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer){
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[RS_LEN * 2];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, RS_LEN, s.length);
        return result;
    }

    /**
     * BC的SM3withSM2验签需要的rs是asn1格式的，这个方法将直接拼接r||s的字节数组转化成asn1格式
     * @param sign in plain byte array
     * @return rs result in asn1 format
     */
    private static byte[] rsPlainByteArrayToAsn1(byte[] sign){
        if(sign.length != RS_LEN * 2) throw new RuntimeException("err rs. ");
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        try {
            return new DERSequence(v).getEncoded("DER");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS){
        // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and have length<=32
        byte[] rs = rOrS.toByteArray();
        if(rs.length == RS_LEN) return rs;
        else if(rs.length == RS_LEN + 1 && rs[0] == 0) return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        else if(rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte)0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }

    /**
     * 拆数字信封
     */
    public static String decryptDigitalEnvelope(PrivateKey privateKey,String digitalEnvelope) throws Exception {
        byte[] sm2Decrypt = sm2Decrypt(privateKey,Hex.decode(digitalEnvelope));
        return Hex.toHexString(sm2Decrypt);
    }

    /**
     * 生成数字信封
     */
    public static String encryptDigitalEnvelope(PublicKey publicKey,String digitalEnvelope) throws Exception {
        byte[] sm2Encrypt = sm2Encrypt(publicKey,Base64.getDecoder().decode(digitalEnvelope));
        return Base64.getEncoder().encodeToString(sm2Encrypt);
    }


}
